﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Xml;
using System.Xml.XPath;

using SemWeb;
using SemWeb.Query;
using Sgml;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.web;
using umbraco.blog4.interfaces;

#if UMB45
using umbraco.presentation.nodeFactory;
#else
using umbraco.NodeFactory;
#endif

namespace umbraco.blog4 {
    public class Post : ApplicationBase, ICommentsContainer {

        private const string RDFNS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
        private const string TBNS = "http://madskills.com/public/xml/rss/module/trackback/";
        private const string DCNS = "http://purl.org/dc/elements/1.1/";

        public const string DT_CommentsContainer = "CommentsContainer";
        public const string DT_TrackBacksContainer = "TrackBacksContainer";

        #region .ctor
        public Post() {
            Document.New += new Document.NewEventHandler(Document_New);
            Document.BeforePublish += new Document.PublishEventHandler(Document_BeforePublish);
            Document.AfterPublish += new Document.PublishEventHandler(Document_AfterPublish);
        }

        /// <summary>
        /// Creates a new blog post with an entry title and the ID of the parent blog.
        /// </summary>
        /// <param name="postName">The title of the new blog post.</param>
        /// <param name="parentBlog">The node ID of the parent blog.</param>
        public Post(string postName, int parentBlog) {
            if (BlogLibrary.IsType(Blog.DocumentTypeName, parentBlog)) {
                Blog = new Blog(parentBlog);
                BaseDocument = Document.MakeNew(postName, Blog.DTBlogPost, User.GetCurrent(), Blog.Id);
            }
        }

        /// <summary>
        /// Retrieves an existing post by its' node ID.
        /// </summary>
        /// <param name="postId"></param>
        public Post(int postId) {
            if (BlogLibrary.IsType(DocumentTypeName, postId))
            {
                BaseDocument = new Document(postId);
                Blog = Blog.Current(BaseDocument);
            }
            else
            {
                throw new InvalidOperationException("");
            }
        }

        /// <summary>
        /// Retrieves an existing post by its' node ID.
        /// </summary>
        /// <param name="postId"></param>
        public Post(Document post) {
            if (BlogLibrary.TypeAliasInherits(DocumentTypeName, post.ContentType.Alias))
            {
                BaseDocument = post;
                Blog = Blog.Current(BaseDocument);
            } else {
                throw new InvalidOperationException("");
            }
        }
        #endregion

        #region Event handlers
        private void Document_New(Document sender, NewEventArgs e) {
            if (BlogLibrary.IsType(DocumentTypeName, sender.Id))
            {
                if (sender.getProperty("postDate") != null) sender.getProperty("postDate").Value = sender.CreateDateTime;
            }
        }
        private void Document_BeforePublish(Document sender, PublishEventArgs e) {
            // Get dateformat
            if (BlogLibrary.IsType(DocumentTypeName, sender.Id)) {
                Post p = new Post(sender.Id);
                Blog b = p.Blog;

                // Version check for post date - whether that's changed
                bool moveByDate, moveByFormat, needsMoving;
                moveByDate = false;
                moveByFormat = false;
                needsMoving = false;
                DocumentVersionList[] dvlCheckDates = sender.GetVersions();
                if (dvlCheckDates.Length > 1) {
                    Guid prevPost = dvlCheckDates[dvlCheckDates.Length - 2].Version;
                    Document oldPost = new Document(p.Id, prevPost);
                    DateTime prevDate = Convert.ToDateTime(oldPost.getProperty("postDate").Value);
                    moveByDate = (prevDate != p.Date);
                }

                // Version check for whether the URL format has changed
                Document blog = new Document(b.Id);
                DocumentVersionList[] dvlCheckFormat = blog.GetVersions();
                if (dvlCheckFormat.Length > 1) {
                    Guid prevBlog = dvlCheckFormat[dvlCheckFormat.Length - 2].Version;
                    Document oldBlog = new Document(b.Id, prevBlog);
                    var prevFormat = oldBlog.getProperty("blogPostDateFolderFormat").Value;
                    moveByFormat = (prevFormat != blog.getProperty("blogPostDateFolderFormat").Value);
                }
                
                // Needs moving if either is true or the parent is not a DateFolder.
                needsMoving = (moveByDate | moveByFormat || !BlogLibrary.IsType(Blog.DTDefault_DateFolder, p.BaseDocument.ParentId));

                if ((p.Date != null) &&
                    (p.Blog != null) &&
                    needsMoving) {

                    // RJF - Currently Unused Code 
                    //string existingUrl = umbraco.library.NiceUrl(p.Id);
                    //if (existingUrl.Length > 0 & existingUrl.LastIndexOf(@"/") > -1)
                    //    existingUrl = existingUrl.Substring(
                    //        existingUrl.LastIndexOf(@"/"),
                    //        existingUrl.Length - existingUrl.LastIndexOf(@"/"));
                    
                    string format = b.EntryDateUrlFormat.Replace("{0}", "");
                    
                    string[] sections = null;
                    if (!string.IsNullOrEmpty(format))
                        sections = p.Date.Date.ToString(format).Split(@"/- ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                    if (sections == null || sections.Length == 1)
                        sections = p.Date.ToString("yyyy/MM/dd").Split(@"/- ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);

                    if (sections.Length > 1) { // We have datefolders...
                        Document n = new Document(b.Id);
                        foreach (string s in sections) {
                            Document found = null;
                            foreach (Document c in n.Children.Where(c => BlogLibrary.IsType(Blog.DTDefault_DateFolder, c.Id)))
                            {
                                if (c.Text == s) { found = c; }
                            }
                            if (found == null) {

                                n = Document.MakeNew(s, p.Blog.DTBlogDateFolder, User.GetCurrent(), n.Id);
                                n.Publish(User.GetCurrent());
                                library.UpdateDocumentCache(n.Id);
                            }
                            else
                            {
                                n = found;
                            }
                        }
                        if (sender.Parent.Id != n.Id)
                        {
                            sender.Move(n.Id);
                            umbraco.library.RefreshContent();
                        }
                    }
                }
            }
        }

        private void Document_AfterPublish(Document sender, PublishEventArgs e) {
            if (BlogLibrary.IsType(DocumentTypeName, sender.Id)) {

                // Publish the Comments and Trackback nodes as well
                if (!sender.Children.Any(c => c.ContentType.Alias == DT_CommentsContainer))
                {
                    library.UpdateDocumentCache(sender.Id);
                    Document _comments = Document.MakeNew(DT_CommentsContainer, DocumentType.GetByAlias(DT_CommentsContainer), User.GetCurrent(), sender.Id);
                    _comments.Publish(User.GetCurrent());
                    library.UpdateDocumentCache(_comments.Id);
                }
                if (!sender.Children.Any(c => c.ContentType.Alias == DT_TrackBacksContainer))
                {
                    library.UpdateDocumentCache(sender.Id);
                    Document _trackbacks = Document.MakeNew(DT_TrackBacksContainer, DocumentType.GetByAlias(DT_TrackBacksContainer), User.GetCurrent(), sender.Id);
                    _trackbacks.Publish(User.GetCurrent());
                    library.UpdateDocumentCache(_trackbacks.Id);
                }

                return;
                // TODO: Fix up this implementation and remove the above return...

                PingServices(sender);

                string urls = BlogLibrary.GetValueRecursively("pingServices", sender.Id);

                if (!string.IsNullOrEmpty(urls)) {
                    string str3;
                    XmlDocument xd = new XmlDocument();

                    try { xd.LoadXml(urls); }
                    catch { }

                    // string name = this.GetValueRecursively("blogName", sender.Id);
                    string str4 = HttpContext.Current.Request.ServerVariables["SERVER_NAME"].ToLower();

                    // Fixes #5526 and #5681
                    library.UpdateDocumentCache(sender.Id);

                    try {
                        str3 = library.NiceUrlFullPath(sender.Id);
                    } catch (Exception) {
                        Log.Add(LogTypes.Debug, sender.Id, "Cound not get 'NiceUrlFullPath' from current application");
                        str3 = "http://" + str4 + "/" + library.NiceUrl(sender.Id);
                    }

                    foreach (XmlNode link in xd.SelectNodes("//link [@type = 'external']")) {
                        string ping = link.Attributes["link"].Value;
                        PingService(ping, sender.Text, str3);
                    }
                }
            }
        }
        #endregion

        #region Properties
        public int Id { get { return BaseDocument.Id; } }
        public string PostName {
            get { return BaseDocument.Text; }
            set { BaseDocument.Text = value; BaseDocument.Save(); BaseDocument.Publish(User.GetCurrent()); }
        }
        public string Content {
            get { return BaseDocument.getProperty("bodyText").Value.ToString(); }
            set { BaseDocument.getProperty("bodyText").Value = value; BaseDocument.Save(); BaseDocument.Publish(User.GetCurrent()); }
        }
        public string Url { get { return umbraco.library.NiceUrl(BaseDocument.Id); } }
        public DateTime Date {
            get { return Convert.ToDateTime(BaseDocument.getProperty("postDate").Value.ToString()); }
            set { BaseDocument.getProperty("postDate").Value = value.ToString(); BaseDocument.Save(); BaseDocument.Publish(User.GetCurrent()); }
        }
        public bool CloseComments {
            get { return BaseDocument.getProperty("closeComments").Value.ToString() == "1"; }
            set { BaseDocument.getProperty("closeComments").Value = value ? 1 : 0; BaseDocument.Save(); }
        }
        public DocumentType Type { get { return Blog.Current(BaseDocument).DTBlogPost; } }
        public Blog Blog { get; private set; }

        public static string DocumentTypeName { get { return "BlogPost"; } }

        public Document BaseDocument { get; private set; }
        public IEnumerable<Comment> Comments
        {
            get
            {
                var commentsContainer = BaseDocument.Children.FirstOrDefault(c => c.ContentType.Alias == DT_CommentsContainer);
                if (commentsContainer != null)
                {
                    // - Do we perhaps just want to check for Blog.DTBlogComment instead? - RJF
                    foreach (var c in commentsContainer.Children.
                        Where(c => BlogLibrary.TypeAliasInherits(Comment.DocumentTypeName, c.ContentType.Alias)))
                            yield return new Comment(c.Id);
                }
            }
        }
        public IEnumerable<TrackBack> TrackBacks
        {
            get {
                var trackBacksContainer = BaseDocument.Children.FirstOrDefault(c => c.ContentType.Alias == DT_TrackBacksContainer);
                if (trackBacksContainer != null)
                {
                    // - Do we perhaps just want to check for Blog.DTBlogTrackBack instead? - RJF
                    foreach (var t in trackBacksContainer.Children.
                        Where(t => BlogLibrary.TypeAliasInherits(TrackBack.DocumentTypeName, t.ContentType.Alias)))
                        yield return new TrackBack(t.Id);
                }
            }
        }

        public int CommentDepth {
            get{
                int depth = 0;
                foreach (var d in Comments) {
                    if (d.CommentDepth > depth)
                        depth = d.CommentDepth;
                }

                return depth;
            }
        }
        #endregion

        #region Helpers
        private void PingServices(Document d) {
            
        }

        private bool FindTrackbacks(string url) {
            bool found = false;

            using (SgmlReader sgr = new SgmlReader())
            {
            }
            // Get contents of URL
            string doc = BlogLibrary.GetUrlContents(url);
            Regex rgxMatchComments = new Regex("<!--(.*?)-->");
            MatchCollection mcr = rgxMatchComments.Matches(doc);
            if (mcr.Count > 0) {
                using (StringReader str = new StringReader(mcr[0].Value))
                {
                    // The real part begins
                    using (MemoryStore ms = new MemoryStore())
                    {
                        ms.Import(new RdfXmlReader(str));
                        XmlDocument xd = new XmlDocument();
                        foreach (Statement st in ms.Select(new Statement(null, TBNS + "ping", null)))
                            Trace.Write(st.Subject.ToString());
                    }
                }
            }
            return found;
        }

        private void PingService(string ping, string name, string url) {
            try {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(ping);
                request.Method = "POST";
                request.ContentType = "text/xml";

                using (Stream stream = (Stream)request.GetRequestStream()) {
                    using (XmlTextWriter xml = new XmlTextWriter(stream, Encoding.UTF8)) {
                        xml.WriteStartDocument();

                        xml.WriteStartElement("methodCall");
                        xml.WriteElementString("methodName", "weblogUpdates.ping");
                        xml.WriteStartElement("params");
                        xml.WriteStartElement("param");
                        xml.WriteElementString("value", name);
                        xml.WriteEndElement();

                        xml.WriteStartElement("param");
                        xml.WriteElementString("value", url);
                        xml.WriteEndElement();
                        xml.WriteEndElement();
                        xml.WriteEndElement();

                        xml.WriteEndDocument();
                    }
                }
            } catch (Exception ex) {
                Log.Add(LogTypes.Debug, -1, String.Format("pinger: url: {0} exception:{1}", ping, ex));
            }
        }
        #endregion

        #region XSLT helpers
        public static XPathNodeIterator Current() {
            try {
                Node n = Node.GetCurrent();
                #if UMB45
                while (!BlogLibrary.IsType(Post.DocumentTypeName, n.Id)) { n = n.Parent; }
#else
                while (!BlogLibrary.IsType(Post.DocumentTypeName, n.Id)) { n = (Node)n.Parent; }
                #endif
                return umbraco.library.GetXmlNodeById(n.Id.ToString());
            } catch {
                throw new InvalidOperationException();
            }
        }

        public XPathNodeIterator XmlComments()
        {
            XmlDocument xd = new XmlDocument();
            XmlNode x = umbraco.xmlHelper.addTextNode(xd, "comments", "");
            var xn = x.CreateNavigator();
            foreach(var comment in Comments)
            {
                xn.AppendChild(library.GetXmlNodeById(comment.Id.ToString()).Current);
            }

            return xd.CreateNavigator().Select(".");
        }

        public XPathNodeIterator XmlTrackbacks()
        {
            XmlDocument xd = new XmlDocument();
            XmlNode x = umbraco.xmlHelper.addTextNode(xd, "comments", "");
            var xn = x.CreateNavigator();
            foreach(var trackback in TrackBacks)
            {
                xn.AppendChild(library.GetXmlNodeById(trackback.ID.ToString()).Current);
            }

            return xd.CreateNavigator().Select(".");
        }

        public static XPathNodeIterator XmlComments(int postId) {
            if (BlogLibrary.IsType(Post.DocumentTypeName, postId)) {
                return new Post(postId).XmlComments();
            } else {
                throw new InvalidOperationException();
            }
        }

        public static XPathNodeIterator XmlTrackbacks(int postId) {
            if (BlogLibrary.IsType(Post.DocumentTypeName, postId))
            {
                return new Post(postId).XmlTrackbacks();
            }
            else
            {
                throw new InvalidOperationException();
            }
        }
        #endregion
    }
}
